package netconf

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"github.com/kataras/golog"
	"regexp"
	"sdn-executor/core/ssh"
	model "sdn-executor/model/netconf"
	"sdn-executor/utils/erros"
	"strings"
	"time"
)

type Client struct {
	session            *ssh.Session
	version            string
	SessionID          int
	ServerCapabilities []string
}

func NewClient(device string, port int16, user string, pwd string) (*Client, *erros.Error) {
	sshClient, err := ssh.NewSimpleClient(device, port, user, pwd)
	if err != nil {
		golog.Errorf("NewSimpleClient failed. err: %+v", err)
		return nil, err
	}
	session, err := sshClient.NewSession(ssh.SubsystemNetconf)
	if err != nil {
		golog.Errorf("NewSession failed. err: %+v", err)
		return nil, err
	}

	nc := &Client{
		session: session,
		version: "v1.0",
	}

	// 接收hello
	serverHello, e := nc.ReceiveHello()
	if e != nil {
		golog.Errorf("nc.ReceiveHello failed. err: %+v", err)
		return nil, e
	}
	nc.SessionID = serverHello.SessionID
	nc.ServerCapabilities = serverHello.Capabilities

	// 发送hello
	nc.SendHello(&model.HelloMessage{Capabilities: model.DefaultCapabilities})

	// 解析服务端版本
	for _, capability := range serverHello.Capabilities {
		if strings.Contains(capability, "urn:ietf:params:netconf:base:1.1") {
			nc.version = "v1.1"
			break
		}
	}

	return nc, nil
}

func (c *Client) SendAndReceive(query *model.Query) ([]byte, *erros.Error) {
	rpc := []byte(query.Rpc)
	c.Send(rpc)
	return c.Receive()
}

func (c *Client) Send(rpc []byte) *erros.Error {

	var message []byte
	if c.version == "v1.1" {
		header := fmt.Sprintf("\n#%d\n", len(string(rpc)))
		message = append(message, header...)
	}

	message = append(message, rpc...)

	// 添加rpc消息分隔符
	message = append(message, []byte(c.determineSeparator())...)

	_, err := c.session.SendBytes(message)
	return err
}

func (c *Client) Receive() ([]byte, *erros.Error) {
	response, _, err := c.session.RcvUntilString(c.determineSeparator(), 30*time.Second, false, false)
	if c.version == "v1.1" {
		re := regexp.MustCompile(`\n#\d+\n`)
		response = re.ReplaceAll(response, []byte(""))

		response = bytes.ReplaceAll(response, []byte(model.MsgSeparator_v11), []byte(""))
	} else {
		response = bytes.ReplaceAll(response, []byte(model.MsgSeparator), []byte(""))
	}

	return response, err
}

func (c *Client) SendHello(hello *model.HelloMessage) error {
	helloStr, err := xml.Marshal(hello)
	if err != nil {
		return err
	}

	header := []byte(xml.Header)
	rpc := append(header, helloStr...)

	return c.Send(rpc)
}

func (c *Client) ReceiveHello() (*model.HelloMessage, *erros.Error) {

	val, err := c.Receive()
	if err != nil {
		golog.Errorf("c.Receive failed. err: %+v", err)
		return nil, err
	}

	golog.Infof("received server hello message: %s", string(val))
	hello := new(model.HelloMessage)
	e := xml.Unmarshal(val, hello)
	if e != nil {
		golog.Errorf("xml.Unmarshal failed. err: %+v", err)
		return hello, erros.New(erros.CodeInternalError, e.Error())
	}
	return hello, nil
}

func (c *Client) determineSeparator() string {
	if c.version == "v1.1" {
		return model.MsgSeparator_v11
	} else {
		return model.MsgSeparator
	}
}

func (c *Client) Close() {
	if c != nil && c.session != nil {
		if c.session.Client != nil {
			c.session.Client.Close()
		}
		c.session.Close()
	}
}
